/*
 * @(#)PiecewiseFunction.java
 *
 * $Date: 2012-08-10 15:33:58 -0500 (Fri, 10 Aug 2012) $
 *
 * Copyright (c) 2011 by Jeremy Wood.
 * All rights reserved.
 *
 * The copyright of this software is owned by Jeremy Wood.
 * You may not use, copy or modify this software, except in
 * accordance with the license agreement you entered into with
 * Jeremy Wood. For details see accompanying license terms.
 *
 * This software is probably, but not necessarily, discussed here:
 * http://javagraphics.java.net/
 *
 * That site should also contain the most recent official version
 * of this software.  (See the SVN repository for more details.)
 */
package com.bric.math.function;

import java.util.HashSet;
import java.util.Iterator;

/** This function pieces several separate functions together.
 * This makes no guarantees about continuity; it is possible this
 * function is discontinuous.
 */
public class PiecewiseFunction implements Function {

    /** This creates a <code>PiecewiseFunction</code> that resembles another
     * function.  This method should be used when the argument <code>f</code>
     * is normally expensive to calculate, and generally predictable/simple
     * in curvature.
     * <p>The pieces of the new <code>PiecewiseFunction</code> are going
     * to be <code>PolynomialFunctions</code>.
     * @param f the function to mimic.
     * @param fDeriv a derivative of <code>f</code>.
     * @param min the left edge of the domain you're interested in mimicking.
     * @param max the right edge of the domain you're interested in mimicking.
     * @param functions the number of functions/partitions the <code>PiecewiseFunction</code> should
     * use.
     */
    public static PiecewiseFunction create(Function f,Function fDeriv,double min,double max,int functions) {
        Function[] array = new Function[functions];
        double[] bounds = new double[functions-1];
        for(int a = 0; a<functions; a++) {
            double minX = min+(max-min)*((a))/((functions));
            double maxX = min+(max-min)*((a+1))/((functions));

            double minY = f.evaluate(minX);
            double maxY = f.evaluate(maxX);

            if(Double.isNaN(minY) || Double.isNaN(maxY)) {
                throw new IllegalArgumentException("f("+minX+") = "+minY+", f("+maxX+") = "+maxY);
            }

            double minYDeriv = fDeriv.evaluate(minX);
            double maxYDeriv = fDeriv.evaluate(maxX);

            if(Double.isNaN(minYDeriv) || Double.isNaN(maxYDeriv) ||
                    Double.isInfinite(minYDeriv) || Double.isInfinite(maxYDeriv)) {
                array[a] = f;
            } else {
                try {
                    array[a] = PolynomialFunction.createFit(new double[] {minX, maxX},
                            new double[] {minY, maxY},
                            new double[] {minYDeriv, maxYDeriv});
                } catch(RuntimeException e) {
                    System.err.println("a = "+a);
                    System.err.println("mixX = "+minX);
                    System.err.println("maxX = "+maxX);
                    System.err.println("minY = "+minY);
                    System.err.println("maxY = "+maxY);
                    System.err.println("minYDeriv = "+minYDeriv);
                    System.err.println("maxYDeriv = "+maxYDeriv);
                    throw e;
                }
            }
            if(a!=0)
                bounds[a-1] = minX;
        }
        return new PiecewiseFunction(array, bounds);
    }

    Function[] functions;
    double[] upperBounds;

    /** If all our pieces are the same width, we can optimize how we locate
     * the right piece for a given x value.
     */
    private double fixedIntervalLength = -1;

    /** Creates a new <code>PiecewiseFunction</code>.
     *
     * @param functions
     * @param upperBounds the right-most edge of functions.  If there
     * are n-elements in <code>functions</code>, there should be (n-1)
     * elements in this array.
     * <P>So if you pass 2 functions [a, b], and this value is the array [k],
     * then for all values less than k: <code>a</code> will be used.  For
     * all values greater than k, <code>b</code> will be be used.
     */
    public PiecewiseFunction(Function[] functions,double[] upperBounds) {
        if(upperBounds.length+1!=functions.length)
            throw new IllegalArgumentException("there should be 1 less upperbounds ("+upperBounds.length+") than functions ("+functions.length+")");

        this.functions = new Function[functions.length];
        System.arraycopy(functions,0,this.functions,0,functions.length);

        this.upperBounds = new double[upperBounds.length];
        System.arraycopy(upperBounds,0,this.upperBounds,0,upperBounds.length);

        if(upperBounds.length>2) {
            double delta = upperBounds[1]-upperBounds[0];
            for(int a = 2; a<upperBounds.length; a++) {
                double k = upperBounds[a]-upperBounds[a-1];
                if(Math.abs(delta-k)>.00000000001) {
                    return;
                }
            }
            fixedIntervalLength = delta;
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("PiecewiseFunction[ ");
        sb.append(" x=(-inf, "+upperBounds[0]+"] "+functions[0]);
        for(int a = 1; a<upperBounds.length; a++) {
            sb.append(", ");
            sb.append(" x=("+upperBounds[a-1]+", "+upperBounds[a]+"] "+functions[a]);
        }
        sb.append(" x=("+upperBounds[upperBounds.length-1]+", +inf) "+functions[functions.length-1]);
        sb.append(" ]");
        return sb.toString();
    }

    public double evaluate(double x) {
        if(fixedIntervalLength>0) {
            double min = upperBounds[0]-fixedIntervalLength;
            double max = upperBounds[upperBounds.length-1]+fixedIntervalLength;
            int index = (int)( (x-min)/(max-min)*functions.length );
            if(index==functions.length)
                index--;
            return functions[index].evaluate(x);
        }

        for(int a = 0; a<upperBounds.length; a++) {
            if(x<upperBounds[a])
                return functions[a].evaluate(x);
        }
        return functions[functions.length-1].evaluate(x);
    }

    public double[] evaluateInverse(double y) {
        //TODO: there are better data structures to use here
        //I believe the gnu trove project has some sets for doubles...
        HashSet<Double> set = new HashSet<Double>();
        for(int a = 0; a<functions.length; a++) {
            double[] x = functions[a].evaluateInverse(y);
            double minX = a==0 ? Double.MIN_VALUE : upperBounds[a-1];
            double maxX = a==functions.length-1 ? Double.MAX_VALUE : upperBounds[a];
            for(int b = 0; b<x.length; b++) {
                if(x[b]>=minX && x[b]<=maxX) {
                    set.add(new Double(x[b]));
                }
            }
        }
        int ctr = 0;
        double[] array = new double[set.size()];
        Iterator<Double> i = set.iterator();
        while(i.hasNext()) {
            array[ctr++] = (i.next()).doubleValue();
        }
        return array;
    }

    public Function getFunction(int index) {
        return functions[index];
    }

    public int getFunctionCount() {
        return functions.length;
    }

    public void setFunction(int index,Function f) {
        functions[index] = f;
    }
}
